/* @flow */

import { install } from './install'
import { START } from './util/route'
import { assert } from './util/warn'
import { inBrowser } from './util/dom'
import { cleanPath } from './util/path'
import { createMatcher } from './create-matcher'
import { normalizeLocation } from './util/location'
import { supportsPushState } from './util/push-state'

import { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
import { AbstractHistory } from './history/abstract'

import type { Matcher } from './create-matcher'

export default class VueRouter {
  static install: () => void;
  static version: string;

  app: any;
  apps: Array<any>;
  ready: boolean;
  readyCbs: Array<Function>;
  options: RouterOptions;
  mode: string;
  history: HashHistory | HTML5History | AbstractHistory;
  matcher: Matcher;
  fallback: boolean;
  beforeHooks: Array<?NavigationGuard>;
  resolveHooks: Array<?NavigationGuard>;
  afterHooks: Array<?AfterNavigationHook>;

  constructor (options: RouterOptions = {}) {
    debugger
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []

    // 创建matcher 匹配函数
    this.matcher = createMatcher(options.routes || [], this)

    // 默认是hash模式
    let mode = options.mode || 'hash'

    // history 模式判断
    // supportsPushState：该函数是判断浏览器是否支持 `history` 模式
    // 如果设置了 `history` 模式，但是浏览器不支持，会回退到 `hash` 模式
    // fallback 是当前浏览器不支持 history.pushState 控制路由是否回退到hash模式
    // 简单来说 是否回退可以通过 fallback 配置项来控制，默认值为 true
    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false

    // 降级处理,设置为hash
    if (this.fallback) {
      mode = 'hash'
    }
    // 非浏览器环境的 history
    if (!inBrowser) {
      mode = 'abstract'
    }
    this.mode = mode

    // 根据不同的模式选择实例化对应的 History 类
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash':
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        // abstract 路由是一种抽象路由，一般用在非浏览器端，维护一种抽象的路由结构，使得能够嫁接在客户端或者服务端等没有 history 路由的地方
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
          // 错误提示
          assert(false, `invalid mode: ${mode}`)
        }
    }
  }
  // 输入参数raw，current，redirectedFrom，结果返回匹配route
  match (
    raw: RawLocation,
    current?: Route,
    redirectedFrom?: Location
  ): Route {
    debugger
    return this.matcher.match(raw, current, redirectedFrom)
  }
  // 是一个get方法，用于获取当前history.current，也就是当前route，包括path、component、meta等
  get currentRoute (): ?Route {
    return this.history && this.history.current
  }

  // install 调用的是这个方法，
  init (app: any /* Vue component instance */) {
    process.env.NODE_ENV !== 'production' && assert(
      install.installed,
      `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
      `before creating root instance.`
    )
    // app为Vue的实例
    // 将vue实例推到apps列表中，install里面最初是将vue根实例推进去的
    this.apps.push(app)

    // set up app destroyed handler
    // https://github.com/vuejs/vue-router/issues/2639
    // 通过$once在 destroyed 钩子函数中，从apps 里将app移除
    app.$once('hook:destroyed', () => {
      // clean out app from this.apps array once destroyed
      const index = this.apps.indexOf(app)
      if (index > -1) this.apps.splice(index, 1)
      // ensure we still have a main app or null if no apps
      // we do not release the router so it can be reused
      if (this.app === app) this.app = this.apps[0] || null
    })

    // main app previously initialized
    // return as we don't need to set up new history listener
    if (this.app) {
      return
    }
    // 在 VueRouter 上挂载 app 属性
    // 可以通过打印this，找到 _router 或者 $router 找到
    // 可以这样说 _router 看到命名规则也值得这个是内部使用的，$router 是提供给外部（用户）使用的
    this.app = app

    const history = this.history
    console.log(this.history)
    // 初始化当前路由，完成第一次导航（transitionTo）
    // transitionTo：进行路由导航的函数，History 类下的（所有模式的父类）
    if (history instanceof HTML5History) {
      // history 模式下对 popstate 事件进行监听
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      // hash 模式会在 transitionTo 函数中调用 setupListeners方法
      // setupListeners 会对haschange 事件进行监听
      const setupHashListener = () => {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }
    // 挂载了回调的cb， 每次更新路由更好更新_route
    history.listen(route => {
      this.apps.forEach((app) => {
        app._route = route
      })
    })
  }

  // 路由跳转之前
  // router.beforeEach((to,from,next)=>{})
  beforeEach (fn: Function): Function {
    return registerHook(this.beforeHooks, fn)
  }
  // 路由导航被确认之间前
  beforeResolve (fn: Function): Function {
    return registerHook(this.resolveHooks, fn)
  }
  // 路由跳转之后
  afterEach (fn: Function): Function {
    return registerHook(this.afterHooks, fn)
  }

  // 第一次路由跳转完成时被调用的回调函数,添加一个回调函数
  onReady (cb: Function, errorCb?: Function) {
    this.history.onReady(cb, errorCb)
  }
  // 路由报错
  onError (errorCb: Function) {
    this.history.onError(errorCb)
  }

  // 关于 push 和 replace 注意，hash 和 history 模式实现方式是类似的，只是类似而已

  // 路由跳转的方法，也就是我们 $router.push 调用的就是这个方法
  // 这个方法会向history栈添加一个记录，点击后退会返回到上一个页面
  push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.push(location, resolve, reject)
      })
    } else {
      this.history.push(location, onComplete, onAbort)
    }
  }
  // 路由替换
  // 这个方法不会向history里面添加新的记录，点击返回，会跳转到上上一个页面。上一个记录是不存在的
  replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.replace(location, resolve, reject)
      })
    } else {
      this.history.replace(location, onComplete, onAbort)
    }
  }

  // 相对于当前页面向前或向后跳转多少个页面
  // 内部调用的就是 window.history.go(n)，n可为正数可为负数，正数返回上一个页面
  go (n: number) {
    this.history.go(n)
  }
  // 返回到上一个页面
  back () {
    this.go(-1)
  }
  // 前进到下一个页面
  forward () {
    this.go(1)
  }

  // 据路径或者路由获取匹配的组件,返回目标位置或是当前路由匹配的组件数组（是组件的定义或构造类，不是实例）
  // 通常在服务端渲染的数据预加载时使用到
  getMatchedComponents (to?: RawLocation | Route): Array<any> {
    const route: any = to
      ? to.matched
        ? to
        : this.resolve(to).route
      : this.currentRoute
    if (!route) {
      return []
    }
    return [].concat.apply([], route.matched.map(m => {
      return Object.keys(m.components).map(key => {
        return m.components[key]
      })
    }))
  }
  // 一般用于解析目标位置
  // 返回包含如下属性的对象
  resolve (
    to: RawLocation,
    current?: Route,
    append?: boolean
  ): {
    location: Location,
    route: Route,
    href: string,
    // for backwards compat
    normalizedTo: Location,
    resolved: Route
  } {
    current = current || this.history.current
    const location = normalizeLocation(
      to,
      current,
      append,
      this
    )
    const route = this.match(location, current)
    const fullPath = route.redirectedFrom || route.fullPath
    const base = this.history.base
    const href = createHref(base, fullPath, this.mode)
    return {
      location,
      route,
      href,
      // for backwards compat
      normalizedTo: location,
      resolved: route
    }
  }
  // 动态新增路由
  addRoutes (routes: Array<RouteConfig>) {
    this.matcher.addRoutes(routes)
    if (this.history.current !== START) {
      this.history.transitionTo(this.history.getCurrentLocation())
    }
  }
}

// 注册钩子函数
function registerHook (list: Array<any>, fn: Function): Function {
  // 先将函数添加到数组中
  list.push(fn)
  // 返回一个函数,从数组中删除fn,也就是在执行完callback后,调用这个方法,在list中删除fn
  return () => {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)
  }
}
// 构建路由在浏览器中显示格式
function createHref (base: string, fullPath: string, mode) {
  var path = mode === 'hash' ? '#' + fullPath : fullPath
  return base ? cleanPath(base + '/' + path) : path
}

// 在VueRouter类上挂载了一个静态的install方法
VueRouter.install = install
VueRouter.version = '__VERSION__'

// 如果已经挂载了Vue则自动去使用这个插件
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}
